Skip to content

PHPC-1709: Add typing information to arginfo #1337

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Aug 3, 2022

Conversation

alcaeus
Copy link
Member

@alcaeus alcaeus commented Jul 22, 2022

PHPC-1708
PHPC-1711
PHPC-2015
PHPC-2115

This PR adds stub files to generate arginfo. As a first proof of concept, this is done for BSON classes, but other classes will follow. Wherever we can't add return types yet (e.g. for interfaces and non-final classes where this constitutes a BC break) we declare a tentative return type to alert people to the upcoming change. In the stub files, this is characterised by the @tentative-return-type annotation in the docblock for the corresponding method.

There are a couple of requirements for this, which will be added to the contribution docs later:

  • arginfo headers have to be generated using build/gen_stub.php. To get this file, phpize needs to have been called with PHP 8.0 or later

  • Changing stub files entails regenerating arginfo files, as well as removing any .lo files for the class in question. I haven't yet managed to define the arginfo files as dependency for the respective class which would enable automatic recompilation.

  • Create stub files for BSON classes

  • Create stub files for remaining classes

  • Update contributing docs

  • Integrate arginfo generation into build process (if possible)

  • Add checks to build process to ensure stub files are up to date (if the above is not possible)

@alcaeus alcaeus self-assigned this Jul 22, 2022
@@ -11,7 +11,7 @@ require_once __DIR__ . '/../utils/basic.inc';

echo throws(function() {
new MongoDB\BSON\Decimal128([]);
}, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n";
}, TypeError::class), "\n";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely sure about these changes - some people may see this as a BC break. I'll have to double-check why the exception class changes for this and ObjectId.

Copy link
Member

@jmikola jmikola Jul 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this change apply to all PHP versions? It calls to mind some of the variations between PHP 7.x and 8.0 when we were implementing PHPC-1631.

The only other example of split tests for TypeError that comes to mind is:

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It affects all PHP versions, since we unconditionally add argument types to methods. I'll take another look at this tomorrow to figure out where this is coming from.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure this is the result of parameter types now being validated by arginfo instead of zpp. That's probably something we should document in release notes (you can add documentation-needed on the ticket and make a note of this in a comment). I don't think there's any place to mention this in the extension docs, though -- it'd be pedantic to add this to every method's changelog.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I believe adding this to each method's changelog would be overkill. Maybe a paragraph in the release notes would be appropriate for this.

@alcaeus alcaeus force-pushed the feature/phpc-2015 branch 2 times, most recently from 3cf104e to 6bfe74d Compare July 25, 2022 07:28
@alcaeus alcaeus requested a review from jmikola July 25, 2022 07:33
@alcaeus
Copy link
Member Author

alcaeus commented Jul 25, 2022

@jmikola this is ready for a first look before I continue with the rest of these classes. Build failures on PHP 8.1 are due to deprecation warnings since we're introducing tentative return types for BSON interfaces. The affected classes will be updated in the next step once we've confirmed that the general direction is ok.

@alcaeus alcaeus force-pushed the feature/phpc-2015 branch from 6bfe74d to 82f3668 Compare July 27, 2022 12:32
Copy link
Member Author

@alcaeus alcaeus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added generate-function-entries to existing stubs and added stubs for exception classes. See a couple of notes below.

final class Binary implements BinaryInterface, JsonSerializable, Type, \Serializable
{
/** @var int */
public const TYPE_GENERIC = 0x01;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added these in a separate commit since we used to use the libbson enum to define these constants. Since these are protected by the BC promise, I don't mind hardcoding the values here but can revert the change if you'd rather use libbson values.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I didn't consider that. Since php_phongo_binary_init_ce isn't going away, I think keeping the zend_declare_class_constant_long lines in there is sensible. This is going to come up with other files as well, and I'd rather depend on the libmongoc constants if possible, since that might also catch a bug on their end.

If not, I'd at least want to have comments in these stub files referring to the libmongoc constant we're hard-coding. For example:

public const TYPE_GENERIC = 0x01; /* BSON_SUBTYPE_BINARY */

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try (may requires 8.2 to generate the arginfo)

/** @cvalue BSON_SUBTYPE_BINARY */
public const TYPE_GENERIC = UNKNOWN;

See php/php-src@1362fef

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that is nice. I was already using the 8.2 file (the previous version didn't handle constants), so I switched to using @cvalue.


INIT_NS_CLASS_ENTRY(ce, "MongoDB\\BSON", "Binary", php_phongo_binary_me);
php_phongo_binary_ce = zend_register_internal_class(&ce);
php_phongo_binary_ce = register_class_MongoDB_BSON_Binary(php_phongo_binary_interface_ce, php_phongo_json_serializable_ce, php_phongo_type_ce, zend_ce_serializable);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is generated by specifying generate-class-entries in the stub file. It can't take care of object handlers, but it allows us to rely on the stub files for the class structure (modifiers, extended classes, implemented interfaces, class and property definitions, etc.).

@alcaeus alcaeus force-pushed the feature/phpc-2015 branch from 82f3668 to b47a633 Compare July 27, 2022 13:32

namespace MongoDB\BSON
{
final class Binary implements BinaryInterface, JsonSerializable, Type, \Serializable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should JsonSerializable be \JsonSerializable? It's not defined in the MongoDB\BSON namespace.


Since php_phongo_binary_init_ce includes:

#if PHP_VERSION_ID >= 80000
	zend_class_implements(php_phongo_binary_ce, 1, zend_ce_stringable);
#endif

Would it make sense to use #if here to define two different class declarations, or perhaps just an extra line between this and the opening { to avoid duplication? Something like:

final class Binary implements BinaryInterface, JsonSerializable, Type, \Serializable
#if PHP_VERSION_ID >= 80000
    , \Stringable
#endif
{

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - I'll change this to \JsonSerializable. I was confused by the php_phongo_json_serializable_ce variable 🤦

Unfortunately, the #if is completely ignored in the class header (both with and without duplication of the class definition in conditionals). We're unfortunately stuck adding this ourselves until we require PHP 8.0, at which point we can add it to the stub files.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See: https://github.com/mongodb/mongo-php-driver/blob/1.14.0/php_phongo.c#L183

We may actually want to continue using php_phongo_json_serializable_ce, since it's fetched during MINIT to avoid problems on some distributions where the JSON extension was not compiled in to PHP and instead loaded dynamically. That was definitely an issue back in the 5.x releases (see: PHPC-878), and it may still apply to 7.x+. We may need to confirm with @remicollet.

And while I'm looking at the above, I think the manual fetching of DateTimeImmutable may be obsolete. I've opened PHPC-2115 to track that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Json is always build statically in 8.x, so this hack is still useful on 7.x

(btw, only there to avoid "undefined symbol" when load order is not the correct one for linux distribution using --enable-rtld-now)


/**
* @generate-class-entries static
* @generate-function-entries static
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also nice to add @generate-legacy-arginfo 80000 to take benefit of compatibility stuff when using 8.2. Example when using SensitiveParameter attributes, see

See https://github.com/pierrejoye/php_zip/blob/master/php81/php_zip.stub.php#L91
And https://github.com/pierrejoye/php_zip/blob/master/php81/php_zip_arginfo.h#L532

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also notice that is some additional compatibility checks are missing, is still possible to add them in gen_stub.php before 8.2.0 GA

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have yet to check the legacy arginfo generation and the difference between files. Since we still support PHP 7.2, I assume we'll want to use @generate-legacy-arginfo 70000?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use 70200 it will also generate a foo_legady_arginfo.h without type hinting.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generate-legacy-arginfo 70200 is not supported, so we could only generate legacy arginfo files for PHP 7.0. Since those don't include any argument types at all (which we want since PHP 7.2 allows for this), we'll just do without legacy arginfo for now. We've got everything we wanted covered this way as well:

  • argument types (mixed and unions only on PHP 8.0+)
  • return types for final methods/classes
  • tentative return types for non-final methods/classes (PHP 8.1+, no return type info in other versions)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generate-legacy-arginfo accepts any PHP_VERISON_ID, "legacy" file is generated as soon as < 80000 ;)

BTW? I really think it should be used, at least for SensitiveParameter.
And hacks for compatibility could probably be proposed in php_src so every ext will be benefit of them.

This is how OpenSource work ;)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$ ./build/gen_stub.php 
In ./src/MongoDB/WriteConcern.stub.php:
Legacy PHP version must be one of: "70000" (PHP 7.0), "80000" (PHP 8.0), "80100" (PHP 8.1), "80200" (PHP 8.2), "70200" provided

I'll take a look at adding a compatibility mode for PHP 7.2, but at the moment it doesn't exist. As it is, legacy arginfo isn't useful for us.

@alcaeus alcaeus force-pushed the feature/phpc-2015 branch from 57ae9f0 to 1a4e1a5 Compare July 28, 2022 06:17
@alcaeus alcaeus force-pushed the feature/phpc-2015 branch from 1a4e1a5 to 5bda12f Compare July 28, 2022 10:33
@alcaeus alcaeus force-pushed the feature/phpc-2015 branch 2 times, most recently from f6a101f to a3a6604 Compare July 29, 2022 06:33
@alcaeus alcaeus force-pushed the feature/phpc-2015 branch 3 times, most recently from 9efe977 to f604e6a Compare August 1, 2022 07:19
alcaeus added 2 commits August 1, 2022 09:37
This is necessary to let the generated arginfo files compile
@alcaeus alcaeus force-pushed the feature/phpc-2015 branch from f604e6a to aff07b4 Compare August 1, 2022 10:23
@alcaeus alcaeus force-pushed the feature/phpc-2015 branch from aff07b4 to d469466 Compare August 1, 2022 10:26
@alcaeus alcaeus changed the title [WIP] PHPC-2015 Use stub files for generating arginfo PHPC-1709: Add typing information to arginfo Aug 1, 2022
@alcaeus alcaeus requested a review from jmikola August 1, 2022 11:08
@@ -11,7 +11,7 @@ require_once __DIR__ . '/../utils/basic.inc';

echo throws(function() {
new MongoDB\BSON\Decimal128([]);
}, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n";
}, TypeError::class), "\n";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure this is the result of parameter types now being validated by arginfo instead of zpp. That's probably something we should document in release notes (you can add documentation-needed on the ticket and make a note of this in a comment). I don't think there's any place to mention this in the extension docs, though -- it'd be pedantic to add this to every method's changelog.

@alcaeus alcaeus force-pushed the feature/phpc-2015 branch from d469466 to 0f40aec Compare August 2, 2022 08:19
@alcaeus alcaeus force-pushed the feature/phpc-2015 branch from 0f40aec to 49e526e Compare August 2, 2022 08:38
@alcaeus alcaeus requested a review from jmikola August 2, 2022 08:38
Copy link
Member

@jmikola jmikola left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub decided to stop rendering the full PR diff, but I reviewed the commits since my last review and it looks like everything has been addressed.

@alcaeus alcaeus merged commit 6887226 into mongodb:master Aug 3, 2022
@alcaeus alcaeus deleted the feature/phpc-2015 branch August 3, 2022 06:42
@andrew-demb
Copy link

Adding return types to interfaces breaks BC, isn't it?

As stated in https://jira.mongodb.org/browse/PHPC-1711

Similar to PHPC-1708, except for interfaces instead of final classes. This is a BC break and can only be done in 2.0, and only after the change has been communicated to users (see PHPC-1710).

Should this change be followed with a major release?

v1.15.0 just broke our implementations of CommandSubscriber due to a lack of a void return type in implementation.
https://github.com/code-tool/jaeger-mongodb-collector/blob/0c81dccc14ca516e506c1f13c4ec8ed67592392f/src/JaegerMongoDbQueryTimeCollector.php#L42

PHP Fatal error: During inheritance of MongoDB\Driver\Monitoring\CommandSubscriber: Uncaught ErrorException: Return type of CodeTool\Jaeger\MongoDb\JaegerMongoDbQueryTimeCollector::commandStarted(MongoDB\Driver\Monitoring\CommandStartedEvent $event) should either be compatible with MongoDB\Driver\Monitoring\CommandSubscriber::commandStarted(MongoDB\Driver\Monitoring\CommandStartedEvent $event): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /.../vendor/code-tool/jaeger-mongodb-collector/src/JaegerMongoDbQueryTimeCollector.php:42
Stack trace:
#0 /.../vendor/code-tool/jaeger-mongodb-collector/src/JaegerMongoDbQueryTimeCollector.php(22): Symfony\Component\DependencyInjection\Dumper\Preloader::Symfony\Component\DependencyInjection\Dumper{closure}(8192, 'Return type of ...', '/var/www/profil...', 42)
#1 /.../vendor/composer/ClassLoader.php(571): include('/var/www/prof...

@jmikola
Copy link
Member

jmikola commented Mar 11, 2023

The 1.15.0 release uses tentative return types for interfaces methods. Our reasoning at the time was that this was a BC-friendly migration path since it'd have no effect on PHP 8.0 and earlier (see: phongo_compat.h).

That said, I now realize that users already on PHP 8.1 would have encountered this issue for the first time when upgrading from driver version 1.14.x to 1.15.0. I apologize for any inconvenience this may have caused on your side.

Should this change be followed with a major release?

In hindsight, it may have been preferable punt on introducing tentative return types altogether. I don't think the change alone would be enough to warrant a bump to 2.0 (there are other larger changes planned, but not yet implemented). Tentative return types perhaps make more sense in PHP core where BC breaks regularly show up in minor releases (e.g. https://www.php.net/manual/en/migration81.incompatible.php), but not PECL extensions which follow external release schedules.

PHPC-2139 is the canonical ticket for BC-breaking typing changes for 2.0. That will be the next opportunity to replace these tentative return types with actual return types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants